<?php
###########################################################################
#  Copyright (C) 2006-2007 Glyphix, Inc. <briz@glyphix.com>               #
#                                                                         #
#  Permission is hereby granted, free of charge, to any person obtaining  #
#  a copy of this software and associated documentation files (the        #
#  "Software"), to deal in the Software without restriction, including    #
#  without limitation the rights to use, copy, modify, merge, publish,    #
#  distribute, sublicense, and/or sell copies of the Software, and to     #
#  permit persons to whom the Software is furnished to do so, subject to  #
#  the following conditions:                                              #
#                                                                         #
#  The above copyright notice and this permission notice shall be         #
#  included in all copies or substantial portions of the Software.        #
#                                                                         #
#  Except as contained in this notice, the name(s) of the above           #
#  copyright holders shall not be used in advertising or otherwise to     #
#  promote the sale, use or other dealings in this Software without       #
#  prior written authorization.                                           #
#                                                                         #
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
#  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
#  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
#  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#  OTHER DEALINGS IN THE SOFTWARE.                                        #
###########################################################################

/**
 * GXPage_SiteConfig
 *
 * Loads Site.xml and provides methods for traversing it and obtaining information about it.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	Engine
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 * @uses		GXPage_SubClass
 */

class GXPage_SiteConfig extends GXPage_SubClass {

	/**
	 * getInstance
	 *
     * Returns the single unique instance of this class.
	 * @return object unique instance of this object
     */
	public static function getInstance(){
		return SingletonPattern::getSingleton( __CLASS__ );
	}

	/**
	 * __construct
	 * Loads the Site.xml document.
	 * @param string $path absolute path to Site.xml
	 * @return object
	 */
	public function __construct( $path = null ){
		// Check for location of config
		if( !is_file(realpath($path)) ){
			trigger_error( 'Missing Site config', E_USER_ERROR );
			return false;
		}
		if( !($conf = new GXDOM($path)) ){
			trigger_error( 'Unable to load Site config', E_USER_ERROR );
			return false;
		}
		$this->setConfig($conf);
		return $this;
	}

	/**
	 * getItem
	 * Returns the XML config for the specified item.
	 * @param string $page (required)
	 * @param string $action (optional)
	 * @return object GXDOM
	 */
	public function getItem( $page = null, $action = null ){
		if( !$page ){
			trigger_error( 'Missing page', E_USER_ERROR );
			return false;
		}

		// return the action if we have it
		if( $action ){
			return $this->getAction($page,$action);
		}
		// otherwise, return the page
		return $this->getPage($page);
	}

	/**
	 * loadItem
	 * Finds the requested item (either page or action) and returns the appropriate object.
	 * @param string $page (required)
	 * @param string $action (optional)
	 * @return object
	 */
	public function loadItem( $page = null, $action = null ){
		if( !$page ){
			trigger_error( 'Missing page', E_USER_ERROR );
			return false;
		}

		// return action
		if( $action ){
			if( $this->getAction($page,$action) ){
				return new GXPage_Action($page,$action);
			}
		}

		// return page
		if( $this->getPage($page,$action) ){
			return new GXPage_Page($page,$action);
		}

		trigger_error( 'Unable to find requested item: ' .$page .'/' .$action, E_USER_ERROR );
		return false;
	}

	/**
	 * exists
	 * Verifies that a particular page/action exists in Site.xml.
	 * @param string $page (required)
	 * @param string $action (optional)
	 * @return array or false on failure
	 */
	public function exists( $page = null, $action = null ){
		// must have a page
		if( !$page ){
			trigger_error( 'Missing page', E_USER_ERROR );
			return false;
		}

		// if no action, just close off the xpath
		$sub = ' ]';
		// if action, include the check for an action within the specified page
		if( $action ){
			$sub = ' and Action[ @ID = "' .$action .'" ] ]';
		}
		$xpath = '//Page[ @ID = "' .$page .'"' .$sub;

		// run the xpath
		return $this->xpath($xpath,0);
	}

	/**
	 * getDefaultPage
	 * Returns the id of the first page in Site.xml.
	 * @return string
	 */
	public function getDefaultPage(){
		$page = $this->getConfig()->exec->xpath('/*/Page[1]/@ID',0);
		return $page->nodeValue;
	}

	/**
	 * getPage
	 * Returns the XML config for the specified page.
	 * @param string $page
	 * @return GXDOM
	 */
	public function getPage( $page = null ){
		if( !$page ){
			trigger_error( 'Missing page', E_USER_ERROR );
			return false;
		}

		$xpath = '//Page[ @ID = "' .$page .'" ]';
		if( !($PageConf = $this->xpath($xpath,0)) ){
			trigger_error( 'Unable to locate that page (' .$page .')', E_USER_ERROR );
			return false;
		}

		return $PageConf;
	}

	/**
	 * getAction
	 * Returns the XML config for the specified action.
	 * @param string $page (required)
	 * @param string $action (required)
	 * @return GXDOM
	 */
	public function getAction( $page = null, $action = null ){
		if( !$page || !$action ){
			trigger_error( 'Missing page or action (' .$page .'/' .$action .')', E_USER_ERROR );
			return false;
		}

		// find the action in Site.xml
		$xpath = '//Page/Action[ ../@ID = "' .$page .'" and @ID = "' .$action .'" ]';
		if( !($ActionConf = $this->xpath($xpath,0)) ){
			trigger_error( 'Unable to locate the requested action: ' .$page .'/' .$action, E_USER_ERROR );
			return false;
		}

		// Reference refers to an action in a different page.
		// Recurse to find the final page/action.
		if( $ref = $ActionConf->exec->xpath('Reference') ){
			$page = $ref->attributes['Page'];
			$page = $ref->attributes['Action'];
			return $this->getAction( $page, $action );
		}

		return $ActionConf;
	}

	/**
	 * getStyleSheet
	 * Returns the stylesheet specified for the provided page or action.
	 * @param string $page (required)
	 * @param string $action (optional)
	 * @return string
	 */
	public function getStyleSheet( $page = null, $action = null ){
		if( !$page ){
			trigger_error( 'Missing page', E_USER_ERROR );
			return false;
		}
		// look for a StyleSheet node in the action; failing that, find it in its parent
		$tmp = null;
		if( $action ){
			$xpath = '//*[ @ID = "' .$action .'" and ancestor-or-self::Page/@ID = "' .$page .'" ]/descendant-or-self::*[1]/StyleSheet';
			// if we get no results from action, unset action so we get the page it's part of
			if( !$tmp = $this->xpath($xpath,0) ){
				unset($action);
			}
		}
		if( !$tmp && (!isset($action) || !$action) ){
			$xpath = '//*[ @ID = "' .$page .'" ]/descendant-or-self::*[1]/StyleSheet';
			$tmp = $this->xpath($xpath,0);
		}
		// if we have a result, return it
		if( $tmp ){
			// get('Theme') is GXPage's Theme object
			$themeBase = GXPage::get('Theme')->xpath('BasePath',0)->nodeValue;
			trigger_error( 'Using StyleSheet (' .$themeBase .$tmp->nodeValue .') found in SiteConfig', E_USER_WARNING );
			return $themeBase .$tmp->nodeValue;
		}

		// if we don't have anything, go with the stylesheet specified in the theme
		return GXPage::get('Theme')->getStyleSheet();
	}

	/**
	 * getAuthorizedTree
	 * Returns each page and action in Site.xml which the current user is authorized to see.
	 * It uses the _GX/GetUser action defined in Site.xml to get the user's roles.
	 * @return string
	 */
	public function getAuthorizedTree(){
		// get the user config
		$u = new GXPage_Action('_GX','GetUser');
		$u = $u->getResult();

		// create the main doc
		$tree = new GXDOM( 'gxpage-get-authorized-pages', $this->getConfig() );

		// get the list of possible roles
		if( $u ){
			// init userRoles
			$userRoles = null;
			// make sure the user is actually authenticated
			trigger_error( 'User module is loaded, processing user Roles', E_USER_WARNING );
			if( !($userRoles = $u->exec->xpath('//Auth',0)) ){
				trigger_error( 'This user has no roles ... there must be an Auth node accessible via //Auth', E_USER_ERROR );
				return false;
			}
			$tree->{'gxpage-user-roles'}( $userRoles );
			//trigger_error( 'User has the following roles: ' .$userRoles->exec->xml(), E_USER_ERROR );
		}

		// make the tree
		if( !($authtree = GXPage::get('GXXSL')->process( $tree, GXPage::get('GXPageXSL'), GXXSL::TO_GXDOM )) ){
			trigger_error( 'Unable to generate tree', E_USER_ERROR );
			return false;
		}

		// get the result
		return $authtree;
	}

	/**
	 * isAuthorized
	 * Determines if the current user is authorized to see the specified page/action.
	 * It uses the _GX/GetUser action defined in Site.xml to get the user's roles.
	 * @param string $page (required)
	 * @param string $action (optional)
	 * @return boolean
	 */
	public function isAuthorized( $page = null, $action = null ){
		if( !$page ){
			trigger_error( 'Missing page', E_USER_ERROR );
			return false;
		}

		// get the item
		$item = ($action) ? $this->getAction($page,$action) : $this->getPage($page,$action);
		// initialize roles xml doc
		$rolesDoc = null;
		// initialize user's roles doc
		$userRoles = null;

		// get the user config
		$u = new GXPage_Action('_GX','GetUser');
		$u = $u->getResult();

		// check for User ... if we don't have one, stop if the page is protected
		if( !$u ){
			trigger_error( 'User is not logged in', E_USER_WARNING );
			// check Trust attribute first
			if( $item->attributes['Trust'] ){
				if( strval($item->attributes['Trust']) == 'Protected' ){
					trigger_error( 'User is not logged in but item is marked Protected', E_USER_ERROR );
					return false;
				}
			}
			// if no Trust attribute but an Auth/Role section, no banana
			if( !$item->attributes['Trust'] && $item->exec->xpath('Auth/*') ){
				trigger_error( 'User is not logged in but item requires one or more Roles', E_USER_ERROR );
				return false;
			}
			return true;
		}

		// create the doc to verify against
		$authdoc = new GXDOM( 'gxpage-check-permissions' );
		// add Auth node, but only if there is one
		$auth = ($item->exec->get()->Auth) ? $item->exec->get()->Auth : null;
		$authitem = $authdoc->{'gxpage-authorize-item'}($item);
		// if we have user, verify Auth records against auth records in the item
		if( $userRoles = $u->exec->xpath('//Auth',0) ){
			$authdoc->{'gxpage-user-roles'}( $userRoles );
			//trigger_error( 'User has the following roles: ' .$userRoles->exec->xml(), E_USER_ERROR );
		}

		// perform the verification using XSL
		if( $r = GXPage::get('GXXSL')->process( $authdoc, GXPage::get('GXPageXSL') ) ){
			trigger_error( 'User is authorized to see this item (' .$page .'/' .$action .')', E_USER_WARNING );
			return true;
		}
		trigger_error( 'User is NOT authorized to see this item (' .$page .'/' .$action .')', E_USER_ERROR );
		return false;
	}

}

?>